package com.atwisdom.star.core.bsl.functionClass.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.fastjson.JSONObject;
import com.atwisdom.star.common.actionType.functionClass.DatabaseActionType;
import com.atwisdom.star.common.util.DataSourceUtil;
import com.atwisdom.star.common.util.DateUtils;
import com.atwisdom.star.common.util.OSUtil;
import com.atwisdom.star.common.util.SpringContextUtil;
import com.atwisdom.star.common.vo.*;
import com.atwisdom.star.config.DynamicDataSource;
import com.atwisdom.star.core.bsl.functionClass.DatabaseBslService;
import com.atwisdom.star.core.dao.pojo.functionClass.DatabaseBackup;
import com.atwisdom.star.core.dao.service.functionClass.DatabaseBackupService;
import com.atwisdom.star.core.dsl.functionClass.DatabaseDslService;
import com.atwisdom.star.core.dsl.modelClass.resource.ResourceInstanceDslService;
import com.atwisdom.star.exception.pojo.BusinessException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.*;

@Service
@Slf4j
public class DatabaseBslServiceImpl implements DatabaseBslService {

    @Value("${db-backup.shellPath}")
    private String shellPath;
    @Value("${db-backup.backupPath}")
    private String backupPath;
    @Value("${db-backup.ip}")
    private String ip;
    @Value("${db-backup.port}")
    private String port;
    @Value("${db-backup.username}")
    private String userName;
    @Value("${db-backup.password}")
    private String password;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private DatabaseBackupService databaseBackupService;
    @Autowired
    private DatabaseDslService databaseDslService;
    @Autowired
    private ResourceInstanceDslService resourceInstanceDslService;


    /**************************************************************************************
     * BSL调用方法入口，在一个事务中组合一组方法；responseFactory为调用组合，execMethod为具体执行方法；
     * 方法传参详见ActionData；方法不同，传参多少不同，但是结构相同
     **************************************************************************************/
    /**
     * 服务工厂方法
     * @param actionDataList 参数
     * @return 参数
     * @throws BusinessException 异常
     */
    @Transactional
    public ResultInfo<?> responseFactory(List<ActionData<JSONObject>> actionDataList) throws BusinessException {
        ResultInfo<?> resultInfo = null;
        for (ActionData<JSONObject> actionData : actionDataList) {
            String action = actionData.getAction();
            DatabaseActionType actionType = DatabaseActionType.valueOf(action);
            String methodName = actionType.getMethodName();
            resultInfo = this.execMethod(methodName, actionData);
        }
        return resultInfo;
    }

    /**
     * 反射执行业务单元方法
     *
     * @param methodName 方法名
     * @param actionData 方法参数
     * @return 返回结果
     * @throws BusinessException 业务异常
     */
    public ResultInfo<?> execMethod(String methodName, ActionData<JSONObject> actionData) throws BusinessException {
        Class<? extends DatabaseBslServiceImpl> clz = this.getClass();
        try {
            Method method = clz.getMethod(methodName, ActionData.class);
            DatabaseBslServiceImpl service = (DatabaseBslServiceImpl) AopContext.currentProxy();
            return (ResultInfo<?>) method.invoke(service, actionData);
        } catch (NoSuchMethodException ex) {
            throw new BusinessException("999999", "没有找到该方法(" + methodName + ")", ex);
        } catch (IllegalAccessException ex) {
            throw new BusinessException("999999", "没有访问该方法权限(" + methodName + ")", ex);
        } catch (InvocationTargetException ex) {
            Throwable targetException = ex.getTargetException();
            if (targetException instanceof BusinessException) {
                BusinessException bex = (BusinessException) targetException;
                throw new BusinessException("999999", bex.getErrorMsg(), ex);
            } else {
                throw new BusinessException("999999", "出错了请重试：(" + methodName + ")---" + ex.getMessage(), ex);
            }
        }
    }


    // @PostConstruct
    private void createShell() throws IOException {
        String backupFileName = shellPath + File.separator + (OSUtil.isLinux() ? "mysql_backup.sh" : "mysql_backup.bat");
        String restoreFileName = shellPath + File.separator + (OSUtil.isLinux() ? "mysql_restore.sh" : "mysql_restore.bat");
        File backupFile = new File(backupFileName);
        File restoreFile = new File(restoreFileName);
        if (!backupFile.exists()) {
            // 创建备份脚本
            String backupShell = getBackupShell();
            Files.write(Paths.get(backupFileName),
                    backupShell.getBytes(StandardCharsets.UTF_8));
        }
        if (!restoreFile.exists()) {
            // 创建还原脚本
            String restoreShell = getRestoreShell();
            Files.write(Paths.get(restoreFileName),
                    restoreShell.getBytes(StandardCharsets.UTF_8));
        }
    }


    /**
     * mysqldump -uroot -proot --default-character-set=utf8 --hex-blob database_name table_name1 table_name2 --result-file=D:\xxx.sql
     * mysqldump -h 110.40.138.207 -P 13306 -u root -pPstarWcloudD$  --default-character-set=utf8 --hex-blob  test_starcloud > backup/test_starcloud_20230823175419.sql
     */
    private String getBackupShell() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("mysqldump").append(" -h ").append("%1")
                .append(" -P").append("%2")
                .append(" --default-character-set=utf8 --hex-blob ")
                .append(" -u").append("%3").append(" -p").append("%4")
                .append(" ").append("%5").append(" --result-file=").append("%6")
        .append(" && exit");
        return stringBuilder.toString();
    }

    private String getRestoreShell() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("mysql").append(" -h ").append("%1")
                .append(" -P").append("%2").append(" -u").append("%3")
                .append(" -p").append("%4").append(" ").append("%5").append(" <").append("%6")
                .append(" && exit");
        return stringBuilder.toString();
    }

    @Override
    public ResultInfo<?> queryByPage(ActionData<JSONObject> actionData) {
        int pageNumber = actionData.getPageNumber();
        int pageSize = actionData.getPageSize();
        String condition = actionData.getCondition();
        String orderBy = actionData.getOrderBy();
        PageInfo pageInfo = databaseDslService.queryByPage(pageNumber, pageSize, condition, orderBy);
        return JsonResult.success(pageInfo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultInfo<?> backup(ActionData<JSONObject> actionData) throws IOException {
        String databaseName = actionData.getData().get(0).getString("databaseName");
        DatabaseBackup databaseBackup = new DatabaseBackup();
        databaseBackup.setBackupDatabase(databaseName);
        String timeNow = DateUtils.getLocalDateFormat(LocalDateTime.now(), "yyyyMMddHHmmss");
        String backupFile =  databaseName + "_" + timeNow + ".sql";
        String[] param = new String[7];
        databaseBackup.setPath(backupPath);
        databaseBackup.setFile(backupFile);
        String shellName = OSUtil.isLinux() ? "mysql_backup.sh" : "mysql_backup.bat";
        databaseBackupService.save(databaseBackup);
        param[0] = shellPath + File.separator + shellName;
        param[1] = ip;
        param[2] = port;
        param[3] = userName;
        param[4] = password;
        param[5] = databaseName;
        param[6] = backupPath + File.separator  + backupFile;
        String command =String.join(" ", param);
        if (OSUtil.isWindows()) {
            command = "cmd /c start " + command;
        }
        log.info("exec command = {}", command);
        Runtime.getRuntime().exec(command);

        return JsonResult.success();
    }

    /**
     * 还原数据库： mysql -h 110.40.138.207 -P13306 -uroot  -p1111 test1 < backup/test_starcloud
     * @param actionData
     * @throws IOException
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultInfo<?> restore(ActionData<JSONObject> actionData) throws IOException {
        String databaseName = actionData.getData().get(0).getString("databaseName");
        String fileName = actionData.getData().get(0).getString("fileName");
        // String databaseName = databaseBackup.getBackupDatabase();
        String shellName = OSUtil.isLinux() ? "mysql_restore.sh" : "mysql_restore.bat";
        String[] param = new String[7];
        param[0] = shellPath + File.separator + shellName;
        param[1] = ip;
        param[2] = port;
        param[3] = userName;
        param[4] = password;
        param[5] = databaseName;
        param[6] = backupPath + File.separator + fileName;
        String command =String.join(" ", param);
        if (OSUtil.isWindows()) {
            command = "cmd /c start " + command;
        }
        log.info("exec command = {}", command);
        Runtime.getRuntime().exec(command);
        return JsonResult.success();
    }


    public Map<String, List<String>> getDataBaseName() throws Exception {
        Connection connection = dataSource.getConnection();
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("show databases");
        List<String> dataBaseNames = new ArrayList<>();
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            int count = metaData.getColumnCount();
            for (int i = 0; i < count; i++) {
                dataBaseNames.add(resultSet.getString(i + 1));
            }
        }

        File file = new File(backupPath);
        if (!file.exists() && !file.isDirectory()) {
            file.mkdir();
        }
        List<String> fileNames = getFileNames(file);
        Map<String, List<String>> resultMap = new HashMap<>();
        resultMap.put("databaseNames", dataBaseNames);
        resultMap.put("fileNames", fileNames);
        return resultMap;
    }


    /**
     * 获取文件夹下所有文件的名称
     * @param file
     * @return
     */
    private List<String> getFileNames(File file) {
        List<String> fileNames = new ArrayList<>();
        File[] files = file.listFiles();
        if (files != null && files.length > 0) {
            for (File f: files) {
                if (!f.isDirectory()) {
                    fileNames.add(f.getName());
                }
            }
        }
        return fileNames;
    }

    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultInfo<?> init(ActionData<JSONObject> actionData) {
        JSONObject data = actionData.getData().get(0);
        Long id = data.getLong("id");
        createDatabase(id);
        updateDataSourceByTenant(id);
        updateTenant(id);
        return JsonResult.success();
    }

    /**
     * 创建数据库
     * @param id
     * @throws SQLException
     */
    private void createDatabase(Long id) throws SQLException {
        JSONObject tenantJson = resourceInstanceDslService.queryById(id);
        JSONObject values = tenantJson.getJSONObject("values");
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(values.getString("datasource_driver"));
        String datasourceUrl = values.getString("datasource_url");
        String sqlUrl = datasourceUrl.substring(0, datasourceUrl.indexOf("?"));
        String databasesUrl = sqlUrl.substring(0, sqlUrl.lastIndexOf("/"));
        String databaseName = sqlUrl.substring(sqlUrl.lastIndexOf("/") + 1);
        dataSource.setUrl(databasesUrl);
        dataSource.setUsername(values.getString("datasource_username"));
        dataSource.setPassword(values.getString("datasource_password"));
        DruidPooledConnection connection = dataSource.getConnection();
        String sql = "create database if not exists "+ databaseName +" default character set utf8mb4 COLLATE utf8mb4_general_ci";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.executeUpdate();
        connection.close();
    }

    /**
     * 更新用户初始化状态为已初始化
     * @param id
     */
    private void updateTenant(Long id) {
        JSONObject instanceJson = new JSONObject();
        JSONObject valuesJson = new JSONObject();
        valuesJson.put("initStatus", true);
        instanceJson.put("values", valuesJson);
        instanceJson.put("id", id);
        resourceInstanceDslService.updateById(instanceJson);
    }

    /**
     * 租户记录的ID， 注意： 不是tenant_id
     * @param id
     */
    @SneakyThrows
    private void updateDataSourceByTenant(Long id) {
        log.info("======动态数据源更新=====");
        DynamicDataSource dynamicDataSource =  SpringContextUtil.getBean("dynamicDataSource");
        DruidDataSource master = SpringContextUtil.getBean("master");
        Map<Object, Object> dataSourceMap = new HashMap<>();
        JSONObject tenantJson = resourceInstanceDslService.queryById(id);
        JSONObject values = tenantJson.getJSONObject("values");
        log.info("tenant = {}", values.toString());
        if (values.getBooleanValue("status") == true && values.getBooleanValue("initStatus") == false) {
            DruidDataSource dataSource = DataSourceUtil.buildDataSource(master, values);
            DruidPooledConnection connection = dataSource.getConnection();
            String account = values.getString("system_account");
            String password = values.getString("system_password");

            ScriptRunner runner = new ScriptRunner(connection);
            // Resources.setCharset(StandardCharsets.UTF_8);
            runner.setLogWriter(null);
            // String sqlUrl = "script" + File.separator + "star-init.sql";
            String sqlUrl = "script" + File.separator + "star-init-simple.sql";
            runner.runScript(new InputStreamReader(new FileInputStream(sqlUrl), StandardCharsets.UTF_8));
            String sql = "INSERT INTO user_account (userName, account, password) VALUES ('管理员', '"+ account +"', '"+password+"')";
            Statement statement = connection.createStatement();
            statement.execute(sql);
            connection.commit();
            connection.close();

            if (StrUtil.isNotBlank(values.getString("tenant_id"))) {
                dataSourceMap.put(values.getString("tenant_id"), dataSource);
            }
            dynamicDataSource.setDataSources(dataSourceMap);
            dynamicDataSource.afterPropertiesSet();
        }
    }


}
